
import random
import string
import base64
import subprocess
from functools import partial

from Crypto.Cipher import DES, AES
from Crypto.Util.Padding import pad, unpad

subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')  # 在导入execjs前加入补丁 解决js编码字符集问题

import execjs
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

from xunter_utils.xunter_requests import ChromeClient


class WY_Music(ChromeClient):
    def __init__(self):
        super().__init__()

    """
    function a(a) {     //分析得出方法a是生成一个16位的随机字符串
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)
            e = Math.random() * b.length,   //0到1中的随机数乘以b的常数 等于说是对b进行了一次缩放
            e = Math.floor(e),          //取整
            c += b.charAt(e);           //charAt索引取值拼接到c中
        return c
    }

    """

    @staticmethod
    def a_random_str(lens: int = 16) -> str:
        b = string.ascii_letters + string.digits  # 调用string方法生成字符串b
        c = ''
        for d in range(lens):
            e = random.randint(0, len(b) - 1)
            c += b[e]
        return c

    """
        //return CryptoJs.DES.encrypt(text,key,cfg).toString();
        对照得出结论 e为text c为key d为iv初始填充向量0102030405060708
        function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)  //key   对a进行parse转换成utf的字节数据  所以b是进行加密时需要的密钥
          , d = CryptoJS.enc.Utf8.parse("0102030405060708") //这个是iv填充初始向量
          , e = CryptoJS.enc.Utf8.parse(a)  //text  对a进行parse转换成utf的字节数据  所以a是要进行加密的文本
          , f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC     //mode为CBC
        });
        return f.toString()
    }
    """

    def b_aes_encrypt(self, a: str, b: str) -> str:
        """
        经过分析js代码得出
        :param a: 进行加密的文本
        :param b: 进行加密时需要的key
        :return:
        """
        iv = '0102030405060708'.encode('utf-8')
        AES_dict = AES.new(key=b.encode('utf-8'), mode=AES.MODE_CBC, iv=iv)  # 创建加密对象
        pad_data = pad(a.encode('utf-8'), AES.block_size)  # 填充字符串数据
        AES_en = AES_dict.encrypt(pad_data)  # 加密
        return base64.b64encode(AES_en).decode()  # base64转换，转字符串

    """
    c方法为RSA加密非对称加密
    很难搞 所以我们一般不用python实现
    而是使用第一种js逆向方式 复制js源码，交给python执行(补充js环境，速度快，难度中等耗时中等)
    这里就要补环境
    function c(a, b, c) {
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    我们先找到setMaxDigits方法发现里面又调用了一个方法  像这种方法里面又调方法的我们最好的解决办法就是
    创建一个js文件将与setMaxDigits方法平级的方法都复制粘贴进去并且还要把里面局部的方法改成全局方法
    接着还要将c方法里面调用的方法也都复制粘贴到创建好的js文件中
    """

    def c_rsa(self, a: str, b: str, c: str):
        """
        将js中的c方法和js文件中的js代码拼接后交给python执行
        就实现了c方法
        :param a:
        :param b:
        :param c:
        :return:
        """
        with open(r'./code.js', 'r', encoding='utf-8') as f:
            js_data = f.read()
        js_data += """
        function c(a, b, c) {
            var d, e;
            return setMaxDigits(131),
            d = new RSAKeyPair(b,"",c),
            e = encryptedString(d, a)
    }
        """
        js = execjs.compile(js_data)
        return js.call('c', a, b, c)

    """
    function d(d, e, f, g) {
        var h = {}      #定义了一个存储对象
        , i = a(16);  #定义了一个a方法
        return h.encText = b(d, g), #b方法
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),   #c方法
        h
    }
    """

    def d_boss(self, ids: int):
        """
        经过分析得出传入的参数中只有id是变化的其他的都是固定值所以只要传一个id进来
        :param ids: 音乐id
        :return:
        """
        # var h = {}  #定义了一个存储对象  , i = a(16);  #定义了一个a方法
        h, i = {}, self.a_random_str()

        d = '{"ids":"[%s]","level":"standard","encodeType":"aac","csrf_token":"38bafc53f6619692319783b5123e1ca9"}' % ids
        e = '010001'
        f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
        g = '0CoJUm6Qyw8W8jud'
        # return h.encText = b(d, g), #b方法
        h['encText'] = self.b_aes_encrypt(d, g)
        # h.encText = b(h.encText, i),
        h['encText'] = self.b_aes_encrypt(h['encText'], i)
        # h.encSecKey = c(i, e, f),  # c方法
        h['encSecKey'] = self.c_rsa(i, e, f)
        return h

    def get_video(self, ids: int):
        bfJ0x = self.d_boss(ids)
        res = self.send_request_post(
            'https://music.163.com/weapi/song/enhance/player/url/v1',
            params = dict(
                csrf_token='38bafc53f6619692319783b5123e1ca9'
            ),
            data = dict(
                params = bfJ0x['encText'],
                encSecKey = bfJ0x['encSecKey'],
            )
        )
        print(res,res.text)
        video_res = self.send_request(res.json()['data'][0]['url'])

        with open(f'{ids}.m4a', 'wb+') as fp:
            fp.write(video_res.content)
        print('下载完毕')

if __name__ == '__main__':
    client = WY_Music()
    client.get_video(1493976789)
